package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/gomodule/redigo/redis"
	"github.com/jasonlvhit/gocron"
)

var (
	edgeRunQueue  chan struct{}
	cloudRunQueue chan struct{}
)

var lock sync.Mutex

var networkDelay float64

func EnvInit() (err error) {
	// load the DNN model infomation
	/*
		todo...
	*/
	return nil
}

// 边缘机器资源利用情况以及云边网络延时情况获取
func resourceUesagePull(ctx context.Context) {
	fileResource, err := os.OpenFile("resourceLog"+time.Now().Format("2006-01-02_15_04_05")+".log", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
	if err != nil {
		log.Fatalln(err)
	}
	recordTime := 0
	for {
		select {
		case <-ctx.Done():
			fileResource.Close()
			return
		default:
			// 每500条记录新开一个文档
			if recordTime >= 500 {
				fileResource.Close()
				fileResource, err = os.OpenFile("resourceLog"+time.Now().Format("2006-01-02_15_04_05")+".log", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
				if err != nil {
					log.Fatalln(err)
				}
				recordTime = 0
			}
			recordTime++
			// Linux 系统下读取 /proc/meminfo 文件，获取系统内存信息
			// meminfo, err := os.ReadFile("/proc/meminfo")
			// if err != nil {
			// 	panic(err)
			// }
			// meminfoLines := strings.Split(string(meminfo), "\n")
			// for _, line := range meminfoLines {
			// 	if strings.HasPrefix(line, "MemTotal:") {
			// 		fmt.Println("Total memory:", line)
			// 	}
			// 	if strings.HasPrefix(line, "MemFree:") {
			// 		fmt.Println("Free memory:", line)
			// 	}
			// }

			// Linux下读取 /proc/loadavg 文件，获取系统负载信息
			// loadavg, err := os.ReadFile("/proc/loadavg")
			// if err != nil {
			// 	panic(err)
			// }
			// loadavgLines := strings.Split(string(loadavg), " ")
			// log.Println("Load average:", loadavgLines[0])

			// 获取操作系统信息
			// hostname, _ := os.Hostname()
			// osInfo := fmt.Sprintf("%s (%s)", runtime.GOOS, hostname)
			// fmt.Println("Operating System:", osInfo)

			// 获取 CPU 核心数量
			// cpuCores := runtime.NumCPU()
			// fmt.Println("Number of CPU cores:", cpuCores)

			// 获取当前程序的内存使用情况
			// var memStats runtime.MemStats
			// runtime.ReadMemStats(&memStats)
			// totalMem := float64(memStats.TotalAlloc) / (1024 * 1024)
			// log.Printf("Total memory used: %.2f MB\n", totalMem)

			startTime := time.Now()
			resp, err := http.Get(NetWorkAddr)
			if err != nil {
				log.Println("请求失败：", err)
			} else {
				endTime := time.Now()
				elapsedTime := endTime.Sub(startTime)
				var bandwidth float64
				networkDelay = float64(elapsedTime)
				bytes := resp.ContentLength
				if bytes == -1 {
					log.Println("无法获取文件大小")
				} else {
					bandwidth = float64(bytes) / elapsedTime.Seconds() / 1024 / 1024
				}
				_, err = fileResource.WriteString(fmt.Sprintf("网络时延：%v, 网络带宽：%v MB/s", elapsedTime, bandwidth) + time.Now().Format("2006-01-02_15:04:05") + "\n")
				if err != nil {
					log.Println(err)
				}
				resp.Body.Close()
			}
			// 休眠 1 秒后再次读取
			time.Sleep(time.Second)
		}
	}
}

// 从Redis中获取任务请求，调度结果通过channel传递到任务处理函数，避免无意义的CPU轮询开销
// 数据先放在Redis中，通过管道传递唯一的k，从而让消费线程执行任务
func taskSchedule(c_redis redis.Conn) error {
	// 临界区，防止调度未结束又到下一调度周期进行重复调度
	lock.Lock()
	defer lock.Unlock()
	n, err := redis.Int(c_redis.Do("llen", TASK_REQ))
	if err != nil {
		return err
	}
	// 计算当前周期最优的DLP分区点
	DLPPoint := func() []int {
		DLPResult := make([]int, ModelNum)
		for i := range DLPResult {
			DLPResult[i] = rand.Intn(ModelLayer[i])
		}
		return DLPResult
	}()
	// execute the scheduling algorithm
	/*
		...
	*/
	// fmt.Println(time.Now().UnixMilli())
	// 顺序放置所到任务，随机进行DLP分区
	var task TaskReq
	for i := 0; i < n; i++ {
		data, err := redis.Bytes(c_redis.Do("rpop", TASK_REQ))
		if err != nil {
			log.Println(err)
		} else {
			json.Unmarshal(data, &task)
			task.Index = DLPPoint[task.AppId]
		}
		task.Startime = time.Now().UnixMilli()
		data, _ = json.Marshal(task)
		if task.Index == 0 { // 直接上云
			_, err = c_redis.Do("lpush", CLOUD_QUEUE, data)
			if err != nil {
				log.Panicln(err)
			} else {
				cloudRunQueue <- struct{}{}
			}
		} else {
			_, err = c_redis.Do("lpush", EDGE_QUEUE, data)
			if err != nil {
				log.Panicln(err)
			} else {
				edgeRunQueue <- struct{}{}
			}
		}
	}
	return nil
}

func edgeTaskProcess(ctx context.Context) {
	cEdge, err := redis.Dial("tcp", REDIS_HOST+":"+REDIS_PORT, redis.DialPassword(PASSWORD))
	if err != nil {
		log.Fatalln("edgeTaskProcess redis dial Error.")
	}
	for {
		select {
		case <-edgeRunQueue:
			data, err := redis.Bytes(cEdge.Do("rpop", EDGE_QUEUE))
			for err != nil {
				log.Println(err)
				data, err = redis.Bytes(cEdge.Do("rpop", EDGE_QUEUE))
			}
			var task TaskReq
			json.Unmarshal(data, &task)
			task.Waittime = time.Now().UnixMilli() - task.Startime
			if err != nil {
				log.Println(err)
			} else {
				// Task to process.
				data, _ = json.Marshal(task)
				contentType := "application/json"
				_, err = http.Post(AppId2URL, contentType, strings.NewReader(string(data)))
				if err != nil {
					log.Printf("edgeTaskProcess Http failed!, err: %v", err)
				}
			}
		case <-ctx.Done():
			return
		}
	}
}

func cloudTaskProcess(ctx context.Context) {
	cCloud, err := redis.Dial("tcp", REDIS_HOST+":"+REDIS_PORT, redis.DialPassword(PASSWORD))
	if err != nil {
		log.Fatalln("cloudTaskProcess redis dial Error.")
	}
	for {
		select {
		case <-cloudRunQueue:
			data, err := redis.Bytes(cCloud.Do("rpop", CLOUD_QUEUE))
			for err != nil {
				log.Println(err)
				data, err = redis.Bytes(cCloud.Do("rpop", CLOUD_QUEUE))
			}
			var task TaskReq
			json.Unmarshal(data, &task)
			task.Waittime = time.Now().UnixMilli() - task.Startime
			if err != nil {
				log.Println()
			} else {
				// Task to process.
				contentType := "application/json"
				data, _ = json.Marshal(task)
				// Not blocking
				go http.Post(AppId2URLCloud, contentType, strings.NewReader(string(data)))
			}
		case <-ctx.Done():
			return
		}
	}

}

func taskResultRecord(ctx context.Context) {
	cResult, err := redis.Dial("tcp", REDIS_HOST+":"+REDIS_PORT, redis.DialPassword(PASSWORD))
	if err != nil {
		log.Fatalln("taskResultRecord redis dial Error.")
	}
	defer cResult.Close()
	taskRecordFile, err := os.OpenFile("taskRecordLog"+time.Now().Format("2006-01-02_15_04_05")+".log", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
	if err != nil {
		log.Fatalln(err)
	}
	recordTime := 0
	for {
		select {
		case <-ctx.Done():
			taskRecordFile.Close()
			return
		default:
			for {
				data, err := redis.Bytes(cResult.Do("rpop", Result_QUEUE))
				if err != nil {
					break
				}
				var taskRecord TaskRecord
				json.Unmarshal(data, &taskRecord)
				if recordTime >= 500 {
					taskRecordFile.Close()
					taskRecordFile, err = os.OpenFile("taskRecordLog"+time.Now().Format("2006-01-02_15_04_05")+".log", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
					if err != nil {
						log.Fatalln(err)
					}
					recordTime = 0
				}
				recordTime++
				// 记录任务相关信息：任务Id, 所用模型编号appID, 执行时的分区点，开始响应的时间，等待时延，计算时延，传输时延，总响应时延，任务响应结束的时间
				taskRecordFile.WriteString(fmt.Sprintf("%v %v %v %v %v %v %v %v %v\n", taskRecord.Id, taskRecord.AppId, taskRecord.Index, time.UnixMilli(taskRecord.Startime).Format("2006-01-02_15_04_05"),
					taskRecord.Waittime, taskRecord.Computetime, taskRecord.Transtime, taskRecord.ResponseTime, time.UnixMilli(taskRecord.Endtime).Format("2006-01-02_15_04_05")))
			}
			time.Sleep(1 * time.Second)
		}
	}
}

func main() {
	err := EnvInit()
	if err != nil {
		log.Fatalf("Env config load failed!, error: %v", err)
	}
	var c_redis redis.Conn
	c_redis, err = redis.Dial("tcp", REDIS_HOST+":"+REDIS_PORT, redis.DialPassword(PASSWORD))
	if err != nil {
		fmt.Println("redis dial Error.", err)
		return
	}
	defer c_redis.Close()
	log.Println("config load success!")

	edgeRunQueue = make(chan struct{}, 1000)
	cloudRunQueue = make(chan struct{}, 1000)

	ctx, cancel := context.WithCancel(context.Background())

	go edgeTaskProcess(ctx)
	go cloudTaskProcess(ctx)
	go resourceUesagePull(ctx)
	go taskResultRecord(ctx)

	s := gocron.NewScheduler()
	log.Println("Task Scheduler start!")
	s.Every(1).Seconds().Do(taskSchedule, c_redis)
	sc := s.Start() // keep the channel
	defer close(sc)

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
	<-c // main函数后台持续运行, 直至接收到os的中断或者其他终止信号， SIGKILL无效
	// 清理系统占用资源
	s.Clear()
	cancel()
	fmt.Println("程序即将关闭，正在释放资源")
	time.Sleep(3 * time.Second)
}
